package com.example.bugsnag.ohos;

import ohos.aafwk.ability.Ability;
import ohos.aafwk.content.Intent;
import ohos.aafwk.content.Operation;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.utils.Color;
import ohos.agp.window.service.WindowManager;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.utils.net.Uri;

import com.bugsnag.ohos.BreadcrumbType;
import com.bugsnag.ohos.Bugsnag;
import com.bugsnag.ohos.ExtraTest;
import com.bugsnag.ohos.Severity;
import com.bugsnag.ohos.util.OhosLog;
import com.example.bugsnag.ohos.foo.CrashyClass;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * BaseCrashyActivity
 *
 * @since 2021-07-06
 */
public class BaseCrashyAbility extends Ability {

    private EventHandler eventHandler;
    private Runnable task;
    private AnimatorValue showSnackAnimator;
    private AnimatorValue hideSnackAnimator;
    private int originalSnackHeight;
    private DirectionalLayout dlSnack;

    static {
        System.loadLibrary("entrypoint");
        System.loadLibrary("bugsnag-ndk");
    }

    private native void crashFromCXX();

    private native void anrFromCXX();


    @Override
    protected void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_main);
        WindowManager.getInstance().getTopWindow().get().setStatusBarColor(getColor(ResourceTable.Color_title_bar_color));
        eventHandler = new EventHandler(EventRunner.getMainEventRunner());
        showSnackAnimator = new AnimatorValue();
        showSnackAnimator.setDuration(100);
        hideSnackAnimator = new AnimatorValue();
        hideSnackAnimator.setDuration(100);

        dlSnack = (DirectionalLayout) findComponentById(ResourceTable.Id_dl_snack);
        originalSnackHeight = dlSnack.getHeight();
        dlSnack.setHeight(0);

        Component fatalCrash = findComponentById(ResourceTable.Id_btn_fatal_crash);
        fatalCrash.setClickedListener(component -> crashUnhandled(component));

        Component nativeCrash = findComponentById(ResourceTable.Id_btn_native_crash);
        nativeCrash.setClickedListener(component -> crashFromCXX());

        Component anr = findComponentById(ResourceTable.Id_btn_anr);
        anr.setClickedListener(component -> {
            int i = 0;
            while (true){

                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                showSnackbar();
                if (i > 1000){
                    i--;
                }
                if (i < 0){
                    i++;;
                }
            }

        });

        Component cxxAnr = findComponentById(ResourceTable.Id_btn_cxx_anr);
        cxxAnr.setClickedListener(component -> anrFromCXX());

        Component nonfatalCrash = findComponentById(ResourceTable.Id_btn_nonfatal_crash);
        nonfatalCrash.setClickedListener(component -> crashHandled(component));

        Component nativeNotify = findComponentById(ResourceTable.Id_btn_native_notify);
        nativeNotify.setClickedListener(component -> {
            notifyNativeHandled(component);
            showSnackbar();
        });

        Component customSeverity = findComponentById(ResourceTable.Id_btn_custom_severity);
        customSeverity.setClickedListener(component -> crashWithCustomSeverity(component));

        Component crashUserDetails = findComponentById(ResourceTable.Id_btn_crash_user_details);
        crashUserDetails.setClickedListener(component -> crashWithUserDetails(component));

        Component withMetaData = findComponentById(ResourceTable.Id_btn_crash_with_metadata);
        withMetaData.setClickedListener(component -> crashWithMetadata(component));

        Component withBreadCrumbs = findComponentById(ResourceTable.Id_btn_crash_with_breadcrumbs);
        withBreadCrumbs.setClickedListener(component -> crashWithBreadcrumbs(component));

        Component customReport = findComponentById(ResourceTable.Id_btn_crash_custom_report);
        customReport.setClickedListener(component -> crashWithCallback(component));

        Component multiProcess = findComponentById(ResourceTable.Id_multi_process_start_btn);
        multiProcess.setClickedListener(component -> startMultiProcessActivity(component));

        Component readTheDocs = findComponentById(ResourceTable.Id_read_the_docs);
        readTheDocs.setClickedListener(component -> readDocs(component));
    }


    void crashUnhandled(Component component) {
        throw CrashyClass.crash("Fatal Crash");
    }

    /**
     * You can call [Bugsnag.notify] to send an error report for exceptions
     * which are already handled by your app.
     */
    void crashHandled(Component component) {
        try {
            throw new RuntimeException("Non-Fatal Crash");
        } catch (RuntimeException e) {
            OhosLog.d("BaseCrashyAbility", "crashHandled click");
            Bugsnag.notify(e, event -> {
                OhosLog.d("BaseCrashyAbility", "event %s", event);
                showSnackbar();
                return true;
            });
        }
    }

    /**
     * Delivers an error notification from native (C/C++) code
     */
    void notifyNativeHandled(Component component) {
        ExtraTest.notifyFromCXXJava();
    }

    /**
     * The severity of error reports can be altered. This can be useful for capturing handled
     * exceptions which occur often but are not visible to the user.
     */
    void crashWithCustomSeverity(Component component) {
        RuntimeException e = new RuntimeException("Error Report with altered Severity");
        Bugsnag.notify(e, event -> {
            event.setSeverity(Severity.ERROR);
            showSnackbar();
            return true;
        });
    }

    /**
     * User details can be added globally, which will then appear in all error reports sent
     * to the Bugsnag dashboard.
     */
    void crashWithUserDetails(Component component) {
        Bugsnag.setUser("123456", "joebloggs@example.com", "Joe Bloggs");
        RuntimeException e = new RuntimeException("Error Report with User Info");
        Bugsnag.notify(e, event -> {
            showSnackbar();
            return true;
        });
    }

    /**
     * Additional metadata can be attached to crash reports. This can be achieved by calling
     * [Bugsnag.notify], as shown below, or registering a global callback
     * with [Configuration.addOnError] that adds metadata to the report.
     */
    void crashWithMetadata(Component component) {
        RuntimeException e = new RuntimeException("Error report with Additional Metadata");
        Bugsnag.notify(e, event -> {
            event.setSeverity(Severity.ERROR);
            event.addMetadata("CustomMetadata", "HasLaunchedGameTutorial", true);
            showSnackbar();
            return true;
        });
    }

    /**
     * Breadcrumbs help track down the cause of an error, by displaying events that happened leading
     * up to a crash. You can log your own breadcrumbs which will display on the Bugsnag Dashboard -
     * activity lifecycle callbacks and system intents are also captured automatically.
     */
    void crashWithBreadcrumbs(Component component) {
        Bugsnag.leaveBreadcrumb("LoginButtonClick");
        Map<String, Object> metadata = new HashMap<>();
        metadata.put("reason", "incorrect password");
        Bugsnag.leaveBreadcrumb("WebAuthFailure", metadata, BreadcrumbType.ERROR);

        RuntimeException e = new RuntimeException("Error Report with Breadcrumbs");
        Bugsnag.notify(e, event -> {
            showSnackbar();
            return true;
        });
    }

    /**
     * When sending a handled error, a callback can be registered, which allows the Error Report
     * to be modified before it is sent.
     */
    void crashWithCallback(Component component) {
        RuntimeException e = new RuntimeException("Customized Error Report");
        Bugsnag.notify(e, event -> {
            List<String> completedLevels = new ArrayList<>();
            completedLevels.add("Level 1 - The Beginning");
            completedLevels.add("Level 2 - Tower Defence");
            completedLevels.add("Level 1 - The Beginning");
            Map<String, String> userDetails = new HashMap();
            userDetails.put("playerName", "Joe Bloggs the Invincible");

            event.addMetadata("CustomMetadata", "HasLaunchedGameTutorial", true);
            event.addMetadata("CustomMetadata", "UserDetails", userDetails);
            event.addMetadata("CustomMetadata", "CompletedLevels", completedLevels);
            showSnackbar();
            return true;
        });
    }

    /**
     * Starts an activity in a different process.
     */
    void startMultiProcessActivity(Component component) {
        Intent intent = new Intent();
        Operation operation = new Intent.OperationBuilder()
            .withBundleName(getBundleName())
            .withAbilityName(MultiProcessAbility.class)
            .build();
        intent.setOperation(operation);
        startAbility(intent);
    }

    private void showSnackbar() {
        showSnackAnimator.setValueUpdateListener((animatorValue, v) -> {
            int height = (int) (originalSnackHeight * v);
            dlSnack.setHeight(height);
        });
        showSnackAnimator.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
            }

            @Override
            public void onStop(Animator animator) {
            }

            @Override
            public void onCancel(Animator animator) {
            }

            @Override
            public void onEnd(Animator animator) {
                if (task != null) {
                    eventHandler.removeTask(task);
                    task = null;
                }
                task = () -> hideSnackBar(false);
                eventHandler.postTask(task, 5000, EventHandler.Priority.IMMEDIATE);
            }

            @Override
            public void onPause(Animator animator) {
            }

            @Override
            public void onResume(Animator animator) {
            }
        });
        showSnackAnimator.start();
    }

    private void hideSnackBar(boolean momentSnack) {
        hideSnackAnimator.setValueUpdateListener((animatorValue, v) -> {
            int height = (int) (originalSnackHeight - originalSnackHeight * v);
            dlSnack.setHeight(height);
        });
        hideSnackAnimator.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
            }

            @Override
            public void onStop(Animator animator) {
            }

            @Override
            public void onCancel(Animator animator) {
            }

            @Override
            public void onEnd(Animator animator) {
                if (momentSnack) {
                    showSnackbar();
                }
            }

            @Override
            public void onPause(Animator animator) {
            }

            @Override
            public void onResume(Animator animator) {
            }
        });
        hideSnackAnimator.start();
    }

    void readDocs(Component component) {
        Uri uri = Uri.parse(String.format("https://docs.bugsnag.com/platforms/android/sdk/"));
        Intent intent = new Intent();
        Operation operation;
        operation = new Intent.OperationBuilder().withAction("android.intent.action.VIEW")
            .withUri(uri)
            .build();
        intent.setOperation(operation);
        startAbility(intent);
    }
}
